红黑树的理解与代码实现

红黑树

        我们知道对于二叉搜索树而言,无法保证树的平衡性,从而使得进行操作的时候时间复杂度在O(logn)与O(n)之间。这样是不稳定的。而2-3树则借助于3-结点和临时的4-结点,通过分解,解决了平衡问题。例如对于插入,在最终是3-结点的情况下,临时构成4-结点,然后再分解成3个2-结点,这样令树高度+1,但是整体平衡性不变。而红黑树就是为了实现自平衡这个功能而对2-3树进行了扩展。而红黑树仍然属于一棵二叉查找树,只是多了红结点与黑结点,来模仿2-结点和3-结点。所以对于二叉搜索树的方法,很多都能在红黑树上使用。而红黑树的关于自平衡的很多理论,来源于2-3树。下面是一棵简单的红黑树:

其实按照标准定义,叶子节点仍然有左右两个引用,也就是说,任何一个结点都一定有2个子树,就算这个子树是空的。而这些结点分为红色与黑色。注意看上面的箭头,也有红黑之分。或者说,我们虽然在定义Node结点的时候,认为某个节点的颜色有红黑之分,但是实际上,这个红黑是指链接是红色还是黑色。

        也就是说,如果某结点的颜色是红色,是说连接到该结点的链接是红色,而根的颜色永远为黑色。

        这里提一嘴,红黑树非常依赖图像演示,推荐找视频或者找各种动图来学习红黑树,能够非常快理解旋转之类的操作。

红黑树与2-3树

        为什么一定要对链接区分红黑?如果就上面的图进行变换,我们不管黑色,将红色的连接平着画,可以得到这个图:

可以看到,这样来表示,树的高度是一致的,如果我们仔细看,是不是可以认为,红色链接连接的2个结点可以组合起来作为一个2-3树中的3-结点。这样,就把一棵红黑树转换成了2-3树。而我们认为2-3树是完美平衡的,那么是否说明了,红黑树也是完美平衡。

        红链接将2个2-结点连接起来构成一个3-结点,黑链接则是2-3树中的普通链接。那么对于红黑树而言,仍然可以使用二叉查找树中的搜索之类的,与颜色无关的方法。而例如插入和删除,我们可以认为在处理过程中,使用2-结点、3-结点、4-结点之间的组合与删除,来完成自平衡。

        《算法》书中认为:这样用红黑链接表示的2-3树,是红黑二叉查找树。

红黑树的性质

        对红黑树有这样的定义:

1.红链接均为左链接。

2.两个红链接不能相连在一起。

3.红黑树是完美黑色平衡,即任意空结点到根节点所经过的黑链接完全一致。

4.空结点的链接颜色是黑色。

第一条有点疑问,因为有的资料说可以将红链接作为右链接,这个文章统一认为是只能为左。因为上面的性质,我们需要额外确定一些性质:

1.根节点的链接颜色必定为黑。这里其实无所谓,因为根没有父链接,但是结点中有color,所以就默认为黑了。

2.我们认为红黑树“有数据”的结点都不是叶子结点。叶子结点只能为null或者NIL。

        既然我们认为红黑树就是2-3树的一种,那么就可以理解为什么不能两个红链接连在一起了,这就像一个临时的4-结点,我们允许其存在,但是为了保证平衡,需要分解。

红黑树的颜色

        其实上面写了一点,虽然我们将颜色定义到结点中,但是只是因为我们无法对链接声明数据结构罢了,应该认为是指向该结点的链接的颜色。

        在实际的代码中,可以用常量来表示红黑,调用一个方法可以判定是黑是红即可。例如:

    private boolean isRed(Node node){
        if(node == null) return false;
        return node.color == RED;
    }

写一个就行了,没必要写个isBlack。具体的实现可以看下面对红黑树的定义以及结点的定义。这里并不是写进Node类,而是RBT类中的方法,因为这样会使得后面的算法更加灵活。

红黑树的声明以及Node结点的实现

        我们可以像容器一样,声明一个“红黑树”,而将Node结点作为其内部类。这样可以保证比较广泛地应用。当然也需要使用泛型,因为不可能固定数据类型。红黑树在Java中是TreeMap和TreeSet的底层,对于Map这种数据结构,我们需要使用Key-Value的结构,那么在结点中,声明两种元素就可以了,为了保证顺序,可以令Key实现comparator接口,从而保证按照Key顺序排序,同时完成Map映射关系。这样,我们可以得到这样的数据结构:

class RBT<Key extends Comparable<Key>, Value>{
    private static final boolean BLACK = false;
    private static final boolean RED = true;

    private Node root;

    private class Node{
        private Key key;
        private Value value;
        private Node left;
        private Node right;
        private boolean color;

        public Node(Key key,Value value){
            this.key = key;
            this.value = value;
            this.color = RED;
        }
    }
}

在Node结点中,我是定义了boolean作为红黑判定,这个可以任意改变,例如int,都可以。而这两个常量是在RBT类里面而不是Node,因为我们需要在其他算法里也使用这些常量。

红黑树自平衡的操作-旋转

        会AVL树的话,可能就非常简单能够理解旋转操作。旋转的作用是在进行例如插入、删除操作的时候,根据不同的情况(主要是因为平衡被破坏了),从而进行不同的旋转操作使得以及破坏平衡的树重新回归平衡。这也是我们说“自平衡”的原因。

        红黑树的话,旋转其实有点类似2-3树中的结点分解与临时合并。这个等后面再分析。旋转其实就是左旋和右旋。接收一个结点,转就完事了。而不同情况下如何调用旋转来保持平衡,这是插入、删除等方

  • 4
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
红黑树是一种自平衡二叉搜索树,它的插入操作相对比较复杂。下面是一个简单的C语言实现红黑树插入的示例代码,供你参考: ```c #include <stdio.h> #include <stdlib.h> // 红黑树节点 typedef struct TreeNode { int value; // 节点值 int color; // 颜色,0表示黑色,1表示红色 struct TreeNode *parent; // 父节点指针 struct TreeNode *left; // 左子节点指针 struct TreeNode *right; // 右子节点指针 } TreeNode; // 创建一个红黑树节点 TreeNode* createTreeNode(int value, int color, TreeNode *parent, TreeNode *left, TreeNode *right) { TreeNode *node = (TreeNode*)malloc(sizeof(TreeNode)); node->value = value; node->color = color; node->parent = parent; node->left = left; node->right = right; return node; } // 获取节点的颜色 int getColor(TreeNode *node) { if (node == NULL) { return 0; // 空节点为黑色 } else { return node->color; } } // 左旋操作 void leftRotate(TreeNode **root, TreeNode *node) { TreeNode *rightChild = node->right; node->right = rightChild->left; if (rightChild->left != NULL) { rightChild->left->parent = node; } rightChild->parent = node->parent; if (node->parent == NULL) { *root = rightChild; } else if (node == node->parent->left) { node->parent->left = rightChild; } else { node->parent->right = rightChild; } rightChild->left = node; node->parent = rightChild; } // 右旋操作 void rightRotate(TreeNode **root, TreeNode *node) { TreeNode *leftChild = node->left; node->left = leftChild->right; if (leftChild->right != NULL) { leftChild->right->parent = node; } leftChild->parent = node->parent; if (node->parent == NULL) { *root = leftChild; } else if (node == node->parent->left) { node->parent->left = leftChild; } else { node->parent->right = leftChild; } leftChild->right = node; node->parent = leftChild; } // 插入操作 void insert(TreeNode **root, int value) { // 创建一个新节点,并初始化为红色 TreeNode *node = createTreeNode(value, 1, NULL, NULL, NULL); // 执行BST插入操作 TreeNode *parent = NULL; TreeNode *current = *root; while (current != NULL) { parent = current; if (value < current->value) { current = current->left; } else { current = current->right; } } node->parent = parent; if (parent == NULL) { *root = node; } else if (value < parent->value) { parent->left = node; } else { parent->right = node; } // 颜色修正 while (getColor(node->parent) == 1) { if (node->parent == node->parent->parent->left) { TreeNode *uncle = node->parent->parent->right; if (getColor(uncle) == 1) { node->parent->color = 0; uncle->color = 0; node->parent->parent->color = 1; node = node->parent->parent; } else { if (node == node->parent->right) { node = node->parent; leftRotate(root, node); } node->parent->color = 0; node->parent->parent->color = 1; rightRotate(root, node->parent->parent); } } else { TreeNode *uncle = node->parent->parent->left; if (getColor(uncle) == 1) { node->parent->color = 0; uncle->color = 0; node->parent->parent->color = 1; node = node->parent->parent; } else { if (node == node->parent->left) { node = node->parent; rightRotate(root, node); } node->parent->color = 0; node->parent->parent->color = 1; leftRotate(root, node->parent->parent); } } if (node == *root) { break; } } (*root)->color = 0; } // 中序遍历输出红黑树 void inorderTraversal(TreeNode *node) { if (node == NULL) { return; } inorderTraversal(node->left); printf("%d(%d) ", node->value, node->color); inorderTraversal(node->right); } int main() { TreeNode *root = NULL; // 插入节点 insert(&root, 10); insert(&root, 20); insert(&root, 30); insert(&root, 15); insert(&root, 18); insert(&root, 25); insert(&root, 40); insert(&root, 12); insert(&root, 11); // 输出红黑树 printf("红黑树:"); inorderTraversal(root); return 0; } ``` 在上面的代码中,我们首先定义了一个红黑树节点结构体,包含节点值、颜色、父节点指针、左子节点指针和右子节点指针等成员。然后,我们实现了创建节点、获取节点颜色、左旋和右旋等操作函数。最后,我们实现了插入操作和输出红黑树的函数,并在main函数中进行了测试。 需要注意的是,红黑树的插入操作相对比较复杂,需要进行颜色修正等操作,代码可能比较难理解。如果有不理解的地方,可以多看一下代码,或者参考一些其他资料。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值